using System;
#if TEST
using Xunit;
#endif

namespace Fadd
{
    /// <summary>
    /// Calculates the number of months and days between two dates (and add the overlapping days).
    /// </summary>
    public struct MonthSpan
    {
        private int _days;
        private int _months;

        /// <summary>
        /// Number of months
        /// </summary>
        public int Months
        {
            get { return _months; }
            set { _months = value; }
        }

        /// <summary>
        /// Number of days
        /// </summary>
        public int Days
        {
            get { return _days; }
            set { _days = value; }
        }

        /// <summary>
        /// Creates a monthspan between two dates
        /// </summary>
        /// <param name="from">from which date to calculate.</param>
        /// <param name="to">end date.</param>
        /// <returns>Number of months and days between the two dates.</returns>
        public static MonthSpan Create(DateTime from, DateTime to)
        {
			// Setup the span to return
			MonthSpan span = new MonthSpan();
			span.Months = 0;
			span.Days = 0;

            // check if we got complete months
            if (from.Month == to.Month && from.Year == to.Year)
            {
                if (from.Day == 1 && to.Day == DateTime.DaysInMonth(to.Year, to.Month))
                {
                    span.Days = 0;
                    span.Months = 1;
                }
                else
                    span.Days = to.Day - from.Day + 1; // add both start and end day
                return span;
            }
           
            DateTime start = from;
            if (from.Day > 1)
            {
                span.Days = DateTime.DaysInMonth(from.Year, from.Month) - from.Day;
                start = from.AddDays(span.Days+1);
            }

            DateTime stop = to;
            if (to.Day < DateTime.DaysInMonth(to.Year, to.Month))
            {
                span.Days += to.Day;
                stop = to.AddDays(0 - to.Day);
            }

            int year = start.Year;
            int month = start.Month;
            while (year < stop.Year || (year == stop.Year && month <= stop.Month))
            {
                ++span.Months;
                ++month;
                if (month < 13) continue;
                month = 1;
                ++year;
            }

            return span;
        }
    }

	
	#if TEST

	/// <summary>
	/// Test fixture for the <see cref="MonthSpan"/> struct
	/// </summary>
	public class MonthSpanTester
	{
        [Fact]
        private static void TestOneMonth()
        {
            DateTime from = new DateTime(2008, 1, 1);
            DateTime to = new DateTime(2008, 1, 31);
			MonthSpan span = MonthSpan.Create(from, to);
            Assert.Equal(1, span.Months);
            Assert.Equal(0, span.Days);

            from = new DateTime(2008, 1, 1);
            to = new DateTime(2008, 2, 1);
            span = MonthSpan.Create(from, to);
            Assert.Equal(1, span.Months);
            Assert.Equal(1, span.Days);
        }

		[Fact]
		private static void TestOneMonthByDays()
		{
			DateTime from = new DateTime(2008, 3, 8);
			DateTime to = new DateTime(2008, 4, 30);

			MonthSpan span = MonthSpan.Create(from, to);
			Assert.Equal(1, span.Months);
            Assert.Equal(23, span.Days);
		}

        [Fact]
        private static void Test1Month5Days()
        {
            DateTime from = new DateTime(2008, 1, 1);
            DateTime to = new DateTime(2008, 2, 6);

			MonthSpan span = MonthSpan.Create(from, to);
            Assert.Equal(1, span.Months);
            Assert.Equal(6, span.Days);
        }

        [Fact]
        private static void Test2Month20Days()
        {
            DateTime from = new DateTime(2008, 1, 1);
            DateTime to = new DateTime(2008, 3, 21);

			MonthSpan span = MonthSpan.Create(from, to);
            Assert.Equal(2, span.Months);
            Assert.Equal(21, span.Days);
        }

        [Fact]
        private static void TestYearLap()
        {
            DateTime from = new DateTime(2008, 10, 1);
            DateTime to = new DateTime(2009, 1, 5);

			MonthSpan span = MonthSpan.Create(from, to);
            Assert.Equal(3, span.Months);
            Assert.Equal(5, span.Days);
        }
		
		[Fact]
		private static void TestMultipleYearLapWithLowerFromMonth()
		{
			DateTime from = new DateTime(2008, 3, 1);
			DateTime to = new DateTime(2011, 9, 5);

			MonthSpan span = MonthSpan.Create(from, to);
			Assert.Equal(42, span.Months);
			Assert.Equal(5, span.Days);
		}

        [Fact]
        private static void TestDaysOnly()
        {
            DateTime from = new DateTime(2008, 10, 20);
            DateTime to = new DateTime(2008, 10, 25);

			MonthSpan span = MonthSpan.Create(from, to);
            Assert.Equal(0, span.Months);
            Assert.Equal(5, span.Days);
        }

        [Fact]
        private static void TestDaysSpannedOnly()
        {
            DateTime from = new DateTime(2008, 10, 20);
            DateTime to = new DateTime(2008, 11, 12);

            MonthSpan span = MonthSpan.Create(from, to);
            Assert.Equal(0, span.Months);
            Assert.Equal(23, span.Days);
        }
	}

	#endif
}
